import tkinter as tk
from tkinter import ttk, scrolledtext, filedialog, messagebox
import edge_tts
import asyncio
import threading
import tempfile
import os
import subprocess


class TextToSpeechApp:
    def __init__(self, root):
        self.root = root
        self.root.title("文本转语音工具")
        self.root.geometry("520x480")
        self.root.resizable(False, False)

        # 创建界面组件
        self.create_widgets()

        # 启动语音加载
        threading.Thread(target=self.load_voices, daemon=True).start()

    def load_voices(self):
        """加载语音列表"""
        try:
            loop = asyncio.new_event_loop()
            asyncio.set_event_loop(loop)
            voices = loop.run_until_complete(edge_tts.list_voices())
            loop.close()

            # 过滤出中文语音
            chinese_voices = [v for v in voices if v['Locale'].startswith('zh')]
            self.voices = chinese_voices
            voice_names = [f"{v['ShortName']} ({v['Gender']})" for v in chinese_voices]

            # 更新UI
            self.root.after(0, lambda: self.update_voice_combo(voice_names))

        except Exception as e:
            self.root.after(0, lambda: self.show_error(
                "语音加载错误", f"无法加载语音列表: {str(e)}"))

    def update_voice_combo(self, voice_names):
        """更新语音下拉菜单"""
        self.voice_combo['values'] = voice_names
        self.voice_combo['state'] = 'readonly'

        # 精确匹配Xiaoxiao
        for i, name in enumerate(voice_names):
            if "Xiaoxiao" in name:
                self.voice_combo.current(i)
                return

    def create_widgets(self):
        """创建界面元素"""
        # 文本输入区
        tk.Label(self.root, text="输入文本:", font=("Arial", 10)).pack(pady=(10, 0), anchor='w', padx=10)
        self.text_area = scrolledtext.ScrolledText(self.root, height=8, font=("Arial", 10))
        self.text_area.pack(fill=tk.BOTH, padx=10, pady=5)
        self.text_area.insert(tk.END, "这此处输入你的文本~")  # 默认文本

        # 声音选择
        tk.Label(self.root, text="选择音色:", font=("Arial", 10)).pack(anchor='w', padx=10, pady=(5, 0))
        self.voice_combo = ttk.Combobox(self.root, state="readonly", width=30)
        self.voice_combo.pack(fill=tk.X, padx=10, pady=5)
        self.voice_combo.set("加载中...")

        # 音色调节面板
        self.create_controls()

        # 按钮框架
        button_frame = tk.Frame(self.root)
        button_frame.pack(fill=tk.X, padx=30, pady=15)

        # 预览按钮
        self.preview_btn = tk.Button(button_frame, text="预览听音",
                                     command=self.on_preview_clicked, bg="#2196F3", fg="white",
                                     font=("Arial", 10, "bold"), height=2, width=10)
        self.preview_btn.pack(side=tk.LEFT, padx=(0, 10))

        # 保存按钮
        self.save_btn = tk.Button(button_frame, text="保存为语音文件",
                                  command=self.on_save_clicked, bg="#4CAF50", fg="white",
                                  font=("Arial", 10, "bold"), height=2)
        self.save_btn.pack(side=tk.RIGHT)

        # 署名
        tk.Label(self.root, text="By JuBoyan", fg="gray", font=("Arial", 8)).pack(side=tk.BOTTOM, pady=5)

    def create_controls(self):
        """创建控制面板"""
        frame = tk.Frame(self.root)
        frame.pack(fill=tk.X, padx=10, pady=5)

        # 语速控制
        tk.Label(frame, text="语速:", font=("Arial", 9)).grid(row=0, column=0, sticky='w', padx=(0, 5))
        self.rate_scale = ttk.Scale(frame, from_=0, to=300, orient=tk.HORIZONTAL, length=150)
        self.rate_scale.set(50)
        self.rate_scale.grid(row=0, column=1)
        self.rate_label = tk.Label(frame, text="50%", font=("Arial", 9), width=4)
        self.rate_label.grid(row=0, column=2)
        self.rate_scale.bind("<Motion>", lambda e: self.update_label("rate", self.rate_scale.get()))
        self.rate_scale.bind("<ButtonRelease-1>", lambda e: self.update_label("rate", self.rate_scale.get()))

        # 音高控制
        tk.Label(frame, text="音高:", font=("Arial", 9)).grid(row=1, column=0, sticky='w', padx=(0, 5), pady=(8, 0))
        self.pitch_scale = ttk.Scale(frame, from_=-100, to=100, orient=tk.HORIZONTAL, length=150)
        self.pitch_scale.set(0)
        self.pitch_scale.grid(row=1, column=1, pady=(8, 0))
        self.pitch_label = tk.Label(frame, text="0Hz", font=("Arial", 9), width=4)
        self.pitch_label.grid(row=1, column=2, pady=(8, 0))
        self.pitch_scale.bind("<Motion>", lambda e: self.update_label("pitch", self.pitch_scale.get()))
        self.pitch_scale.bind("<ButtonRelease-1>", lambda e: self.update_label("pitch", self.pitch_scale.get()))

        # 音量控制
        tk.Label(frame, text="音量:", font=("Arial", 9)).grid(row=2, column=0, sticky='w', padx=(0, 5), pady=(8, 0))
        self.volume_scale = ttk.Scale(frame, from_=0, to=100, orient=tk.HORIZONTAL, length=150)
        self.volume_scale.set(100)
        self.volume_scale.grid(row=2, column=1, pady=(8, 0))
        self.volume_label = tk.Label(frame, text="100%", font=("Arial", 9), width=4)
        self.volume_label.grid(row=2, column=2, pady=(8, 0))
        self.volume_scale.bind("<Motion>", lambda e: self.update_label("volume", self.volume_scale.get()))
        self.volume_scale.bind("<ButtonRelease-1>", lambda e: self.update_label("volume", self.volume_scale.get()))

    def update_label(self, control_type, value):
        """更新控制值显示"""
        int_value = int(value)

        if control_type == "rate":
            self.rate_label.config(text=f"{int_value}%")
        elif control_type == "pitch":
            self.pitch_label.config(text=f"{int_value}Hz")
        elif control_type == "volume":
            self.volume_label.config(text=f"{int_value}%")

    def on_preview_clicked(self):
        """预览按钮点击事件 - 使用系统播放器"""
        text = self.text_area.get("1.0", tk.END).strip()
        if not text:
            self.show_warning("输入错误", "请输入要转换的文本")
            return

        if not hasattr(self, 'voices') or not self.voices:
            self.show_warning("加载错误", "语音列表尚未加载完成，请稍候")
            return

        voice_idx = self.voice_combo.current()
        if voice_idx == -1:
            self.show_warning("选择错误", "请选择音色")
            return

        try:
            voice = self.voices[voice_idx]['ShortName']
        except Exception:
            self.show_error("语音错误", "无法获取选中的语音，请重新选择")
            return

        # 格式化参数
        rate = self.format_rate_param()
        pitch = self.format_pitch_param()
        volume = self.format_volume_param()

        # 更新按钮状态
        self.preview_btn.config(state=tk.DISABLED, text="准备中...", bg="#FF9800")

        # 在后台线程中执行预览任务
        threading.Thread(target=self.run_preview,
                         args=(text, voice, rate, pitch, volume),
                         daemon=True).start()

    def run_preview(self, text, voice, rate, pitch, volume):
        """运行预览任务 - 使用系统播放器"""
        loop = asyncio.new_event_loop()
        asyncio.set_event_loop(loop)

        try:
            # 创建临时文件路径
            with tempfile.NamedTemporaryFile(suffix='.mp3', delete=False) as temp_file:
                temp_filename = temp_file.name

            # 生成并保存语音
            communicate = edge_tts.Communicate(
                text=text,
                voice=voice,
                rate=rate,
                pitch=pitch,
                volume=volume
            )

            # 保存为MP3格式
            loop.run_until_complete(communicate.save(temp_filename))

            # 使用系统播放器打开文件
            self.play_with_system_player(temp_filename)

            # 恢复按钮状态
            self.root.after(0, lambda: self.preview_btn.config(
                state=tk.NORMAL, text='预览听音', bg="#2196F3"))

            # 询问是否删除临时文件
            self.root.after(0, lambda: self.ask_to_delete_file(temp_filename))

        except Exception as e:
            error_msg = f"预览失败: {str(e)}"
            self.root.after(0, lambda msg=error_msg: self.show_error("预览错误", msg))
            # 恢复按钮状态
            self.root.after(0, lambda: self.preview_btn.config(
                state=tk.NORMAL, text='预览听音', bg="#2196F3"))
        finally:
            loop.close()

    def play_with_system_player(self, filename):
        """使用系统默认播放器打开文件"""
        try:
            # Windows系统
            if os.name == 'nt':
                os.startfile(filename)
            # macOS系统
            elif os.name == 'posix' and platform.system() == 'Darwin':
                subprocess.Popen(['open', filename])
            # Linux系统
            else:
                subprocess.Popen(['xdg-open', filename])

            # 通知用户
            self.root.after(0, lambda: self.show_info("预览提示",
                                                      "音频文件已用系统默认播放器打开。播放完成后请在播放器中手动关闭。"))

        except Exception as e:
            error_msg = f"无法打开播放器: {str(e)}"
            self.root.after(0, lambda msg=error_msg: self.show_error("播放错误", msg))

    def ask_to_delete_file(self, filename):
        """询问用户是否删除临时文件"""
        if messagebox.askyesno("清理临时文件",
                               "是否删除预览生成的临时音频文件？\n\n文件位置: " + filename):
            try:
                os.unlink(filename)
            except:
                pass

    def on_save_clicked(self):
        """保存按钮点击事件"""
        text = self.text_area.get("1.0", tk.END).strip()
        if not text:
            self.show_warning("输入错误", "请输入要转换的文本")
            return

        if not hasattr(self, 'voices') or not self.voices:
            self.show_warning("加载错误", "语音列表尚未加载完成，请稍候")
            return

        voice_idx = self.voice_combo.current()
        if voice_idx == -1:
            self.show_warning("选择错误", "请选择音色")
            return

        try:
            voice = self.voices[voice_idx]['ShortName']
        except Exception:
            self.show_error("语音错误", "无法获取选中的语音，请重新选择")
            return

        # 格式化参数
        rate = self.format_rate_param()
        pitch = self.format_pitch_param()
        volume = self.format_volume_param()

        # 选择保存位置
        file_path = filedialog.asksaveasfilename(
            defaultextension=".mp3",
            filetypes=[("MP3文件", "*.mp3"), ("WAV文件", "*.wav"), ("所有文件", "*.*")]
        )

        if not file_path:
            return

        # 更新按钮状态
        self.save_btn.config(state=tk.DISABLED, text="处理中...", bg="#FF9800")

        # 启动转换线程
        threading.Thread(target=self.run_conversion,
                         args=(text, voice, rate, pitch, volume, file_path),
                         daemon=True).start()

    def format_rate_param(self):
        """语速参数格式化"""
        try:
            rate_value = int(self.rate_scale.get())
            return f"{rate_value}%" if rate_value < 0 else f"+{rate_value}%"
        except:
            return "+0%"

    def format_pitch_param(self):
        """音高参数格式化"""
        try:
            pitch_value = int(self.pitch_scale.get())
            return f"{pitch_value}Hz" if pitch_value < 0 else f"+{pitch_value}Hz"
        except:
            return "0Hz"

    def format_volume_param(self):
        """音量参数格式化"""
        try:
            volume_value = int(self.volume_scale.get())
            return f"{volume_value}%" if volume_value < 0 else f"+{volume_value}%"
        except:
            return "+0%"

    def run_conversion(self, text, voice, rate, pitch, volume, file_path):
        """运行转换任务"""
        loop = asyncio.new_event_loop()
        asyncio.set_event_loop(loop)

        try:
            # 执行转换
            loop.run_until_complete(self.convert_text_to_speech(
                text, voice, rate, pitch, volume, file_path))

            # 成功后显示信息
            self.root.after(0, self.show_success)

        except Exception as e:
            err_msg = f"转换失败: {str(e)}"
            self.root.after(0, lambda msg=err_msg: self.show_error("转换失败", msg))
        finally:
            loop.close()
            self.root.after(0, self.restore_button)

    async def convert_text_to_speech(self, text, voice, rate, pitch, volume, output_file):
        """执行文本转语音转换"""
        communicate = edge_tts.Communicate(
            text=text,
            voice=voice,
            rate=rate,
            pitch=pitch,
            volume=volume
        )
        await communicate.save(output_file)

    def restore_button(self):
        """恢复保存按钮状态"""
        self.save_btn.config(state=tk.NORMAL, text='保存为语音文件', bg="#4CAF50")

    def show_success(self):
        """显示成功消息"""
        messagebox.showinfo("成功", "语音文件保存成功!")

    def show_info(self, title, message):
        """显示信息"""
        messagebox.showinfo(title, message)

    def show_warning(self, title, message):
        """显示警告"""
        messagebox.showwarning(title, message)

    def show_error(self, title, message):
        """显示错误"""
        messagebox.showerror(title, message)


if __name__ == "__main__":
    import platform

    root = tk.Tk()
    app = TextToSpeechApp(root)
    root.mainloop()